package org.tbstore.client;

import org.apache.log4j.Logger;

import java.io.IOException;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import java.util.ArrayList;
import java.util.List;

public class TBStoreAdministrator {
    private static final Logger         log                = Logger.getLogger(TBStoreAdministrator.class);
    public static final String          DEFAULT_REGION     = "default";
    public static final int             DEFAULT_TTL        = 86400;
    public static final int             MAX_CONNECT_COUNT  = 6;
    private static TBStoreAdministrator instance           = new TBStoreAdministrator();
    private List                        serverNamesList    = new ArrayList(4);
    private List                        connectPool        = new ArrayList(MAX_CONNECT_COUNT);
    private long                        lastConnectionTime = 0;

    public void init(List l) {
        if (l == null) {
            return;
        }

        synchronized (serverNamesList) {
            for (int i = 0; i < l.size(); i++) {
                String s     = (String) l.get(i);
                int    index = s.indexOf(':');

                if (index == -1) {
                    continue;
                }

                StoreServerConfig config = new StoreServerConfig();

                config.setServerIP(s.substring(0, index));

                int port = Integer.parseInt(s.substring(index + 1));

                config.setPort(port);
                serverNamesList.add(config);
            }

            for (int i = 0; i < connectPool.size(); i++) {
                StoreServer server = (StoreServer) connectPool.get(i);

                server.close();
            }

            connectPool.clear();
        }
    }

    public void clearServer() {
        serverNamesList.clear();
    }

    public List getServerNamesList() {
        return serverNamesList;
    }

    public static TBStoreAdministrator getInstance() {
        return instance;
    }

    public TBStoreAdministrator() {
    }

    private void releaseStoreServer(StoreServer s) {
        if (s != null) {
            s.setUsed(false);

            int size      = connectPool.size();
            int closeSize = size - MAX_CONNECT_COUNT;

            if ((closeSize <= 0) || (lastConnectionTime > (System.currentTimeMillis() - 60000))) {
                return;
            }

            for (int i = size - 1; i >= 0; i--) {
                StoreServer server = (StoreServer) connectPool.get(i);

                synchronized (server) {
                    if (server.isUsed()) {
                        continue;
                    }

                    server.setUsed(true);
                    server.close();
                    connectPool.remove(i);
                    closeSize--;

                    if (closeSize <= 0) {
                        break;
                    }
                }
            }
        }
    }

    private StoreServer getStoreServer() {
        int size = connectPool.size();

        try {
            for (int i = 0; i < size; i++) {
                StoreServer server = (StoreServer) connectPool.get(i);

                synchronized (server) {
                    if (!server.isUsed()) {
                        server.setUsed(true);
                        return server;
                    }
                }
            }
        } catch (IndexOutOfBoundsException e) {
        }

        int serverSize = serverNamesList.size();

        if (serverSize > 0) {
            int               index  = (int) ((System.currentTimeMillis()
                                       + Thread.currentThread().hashCode() + Math.round(111)) % serverSize);
            StoreServerConfig config = (StoreServerConfig) serverNamesList.get(index);
            StoreServer       s      = new StoreServer(config.getServerIP(), config.getPort());

            try {
                s.open();
            } catch (IOException e) {
                log.warn(e, e);
            } catch (InterruptedException e) {
                log.warn(e, e);
            }

            s.setUsed(true);
            connectPool.add(s);
            lastConnectionTime = System.currentTimeMillis();

            log.warn(connectPool.size() + ". TBStore Connected => " + config.getServerIP());
            return s;
        }

        return null;
    }

    public Object get(Object key, ICacheLoader cacheLoader) {
        return get(key, cacheLoader, DEFAULT_REGION);
    }

    public Object get(Object key, ICacheLoader cacheLoader, String region) {
        int total = serverNamesList.size();

        if (total == 0) {
            if (cacheLoader != null) {
                try {
                    return cacheLoader.loadCacheObject(key, region);
                } catch (CachingException e1) {
                    log.warn(e1, e1);
                }
            }

            return null;
        }

        Object      object = null;
        StoreServer server = null;

        try {
            String cacheObjectKey = null;

            if (key instanceof ICacheKey) {
                cacheObjectKey = ((ICacheKey) key).getCacheKey();
            } else {
                cacheObjectKey = key.toString();
            }

            MessageDigest alga = MessageDigest.getInstance("MD5");

            alga.update(region.getBytes());
            alga.update(cacheObjectKey.getBytes());

            byte[] digest = alga.digest();

            server = getStoreServer();

            if (server == null) {
                return null;
            }

            object = null;

            try {
                object = server.getObject(digest);
            } catch (CachingException e1) {
                log.warn(e1, e1);
            }

            if ((object == null) && (cacheLoader != null)) {
                object = cacheLoader.loadCacheObject(key, region);

                if (object != null) {
                    try {
                        server.putObject(digest, object, DEFAULT_TTL);
                    } catch (CachingException e1) {
                        log.warn(e1, e1);
                    }
                }
            }
        } catch (Exception e) {
            if ((object == null) && (cacheLoader != null)) {
                try {
                    object = cacheLoader.loadCacheObject(key, region);
                } catch (CachingException e1) {
                    log.warn(e1, e1);
                }
            }

            log.warn(e, e);
        } finally {
            releaseStoreServer(server);
        }

        return object;
    }

    public boolean put(Object key, Object object) {
        return put(key, object, DEFAULT_TTL, DEFAULT_REGION);
    }

    public boolean put(Object key, Object object, int ttl) {
        return put(key, object, ttl, DEFAULT_REGION);
    }

    public boolean put(Object key, Object object, int ttl, String region) {
        int total = serverNamesList.size();

        if (total == 0) {
            return false;
        }

        StoreServer server = null;

        try {
            String cacheObjectKey = null;

            if (key instanceof ICacheKey) {
                cacheObjectKey = ((ICacheKey) key).getCacheKey();
            } else {
                cacheObjectKey = key.toString();
            }

            MessageDigest alga;

            alga = MessageDigest.getInstance("MD5");
            alga.update(region.getBytes());
            alga.update(cacheObjectKey.getBytes());

            byte[] digest = alga.digest();

            server = getStoreServer();

            if (server == null) {
                return false;
            }

            if (server.putObject(digest, object, ttl) == StoreServer.EXIT_SUCCESS) {
                return true;
            }
        } catch (NoSuchAlgorithmException e) {
            log.warn(e, e);
        } catch (Exception e) {
            log.warn(e, e);
        } finally {
            releaseStoreServer(server);
        }

        return false;
    }

    public boolean remove(Object key) {
        return remove(key, DEFAULT_REGION);
    }

    public boolean remove(Object key, String region) {
        int total = serverNamesList.size();

        if (total == 0) {
            return false;
        }

        StoreServer server = null;

        try {
            String cacheObjectKey = null;

            if (key instanceof ICacheKey) {
                cacheObjectKey = ((ICacheKey) key).getCacheKey();
            } else {
                cacheObjectKey = key.toString();
            }

            MessageDigest alga;

            alga = MessageDigest.getInstance("MD5");
            alga.update(region.getBytes());
            alga.update(cacheObjectKey.getBytes());

            byte[] digest = alga.digest();

            server = getStoreServer();

            if (server == null) {
                return false;
            }

            if (server.removeObject(digest) == StoreServer.EXIT_SUCCESS) {
                return true;
            }
        } catch (NoSuchAlgorithmException e) {
            log.warn(e, e);
        } catch (Exception e) {
            log.warn(e, e);
        } finally {
            releaseStoreServer(server);
        }

        return false;
    }

    public boolean incCount(List list) {
        int total = serverNamesList.size();

        if (total == 0) {
            return false;
        }

        StoreServer server = null;

        try {
            server = getStoreServer();

            if (server == null) {
                return false;
            }

            if (server.incCount(list) == StoreServer.EXIT_SUCCESS) {
                return true;
            }
        } catch (Exception e) {
            log.warn(e, e);
        } finally {
            releaseStoreServer(server);
        }

        return false;
    }
}
